package com.xy.file.controller;


import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.ReflectUtil;
import com.xy.common.core.constant.ErrorCodeEnum;
import com.xy.common.core.object.ResponseResult;
import com.xy.common.core.upload.BaseUpDownloader;
import com.xy.common.core.upload.UpDownloaderFactory;
import com.xy.common.core.upload.UploadResponseInfo;
import com.xy.common.core.upload.UploadStoreInfo;
import com.xy.common.core.util.MyCommonUtil;
import com.xy.common.core.util.MyModelUtil;
import com.xy.common.log.annotation.OperationLog;
import com.xy.common.log.model.constant.SysOperationLogType;
import com.xy.common.redis.cache.SessionCacheHelper;
import com.xy.file.strategy.FileStrategy;
import com.xy.webadmin.config.ApplicationConfig;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Tag(name = "文件通用接口")
@Slf4j
@RestController
@RequestMapping("/admin/app/file")
public class FileController {
    @Resource
    private ApplicationConfig appConfig;
    @Resource
    private SessionCacheHelper cacheHelper;
    @Resource
    private UpDownloaderFactory upDownloaderFactory;


    @Resource
    private Map<String, FileStrategy<?>> fileStrategyMap;


    /**
     * 文件上传操作。
     *
     * @param fieldName  上传文件名。
     * @param asImage    是否作为图片上传。如果是图片，今后下载的时候无需权限验证。否则就是附件上传，下载时需要权限验证。
     * @param uploadFile 上传文件对象。
     */
    @SaCheckLogin()
    @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false)
    @PostMapping("/upload")
    public void upload(
            @RequestParam String className,
            @RequestParam String fieldName,
            @RequestParam Boolean asImage,
            @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {

        FileStrategy<?> fileStrategy = fileStrategyMap.get(className);

        if (fileStrategy == null) {
            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
                    ResponseResult.error(ErrorCodeEnum.FILE_STRATEGY_NOT_FOUND));
        }

        Class<?> clazz = fileStrategy.getStrategyClass();

        UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(clazz, fieldName);

        // 这里就会判断参数中指定的字段，是否支持上传操作。
        if (!storeInfo.isSupportUpload()) {
            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
                    ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
            return;

        }
        // 根据字段注解中的存储类型，通过工厂方法获取匹配的上传下载实现类，从而解耦。
        BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());

        UploadResponseInfo responseInfo = upDownloader.doUpload(null,
                appConfig.getUploadFileBaseDir(), clazz.getSimpleName(), fieldName, asImage, uploadFile);

        if (Boolean.TRUE.equals(responseInfo.getUploadFailed())) {
            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
                    ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
            return;
        }
        cacheHelper.putSessionUploadFile(responseInfo.getFilename());

        ResponseResult.output(ResponseResult.success(responseInfo));

        // return ResponseResult.success(responseInfo.getFilename());
    }

    /**
     * 附件文件下载。
     * 这里将图片和其他类型的附件文件放到不同的父目录下，主要为了便于今后图片文件的迁移。
     *
     * @param id        附件所在记录的主键Id。
     * @param fieldName 附件所属的字段名。
     * @param filename  文件名。如果没有提供该参数，就从当前记录的指定字段中读取。
     * @param asImage   下载文件是否为图片。
     * @param response  Http 应答对象。
     */
    @SaIgnore
    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
    @GetMapping("/download")
    public void download(
            @RequestParam String className,
            @RequestParam(required = false) Integer id,
            @RequestParam String fieldName,
            @RequestParam String filename,
            @RequestParam Boolean asImage,
            HttpServletResponse response) throws IOException {
        FileStrategy<?> fileStrategy = fileStrategyMap.get(className);

        if (fileStrategy == null) {
            ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
                    ResponseResult.error(ErrorCodeEnum.FILE_STRATEGY_NOT_FOUND));
        }

        Class<?> clazz = fileStrategy.getStrategyClass();

        if (MyCommonUtil.existBlankArgument(fieldName, filename, asImage)) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        // 使用try来捕获异常，是为了保证一旦出现异常可以返回500的错误状态，便于调试。
        // 否则有可能给前端返回的是200的错误码。
        try {
            // 如果请求参数中没有包含主键Id，就判断该文件是否为当前session上传的。
            if (id == null) {
                if (!cacheHelper.existSessionUploadFile(filename)) {
                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            } else {
                Object model = fileStrategy.getById(id);

                String fieldJsonData = (String) ReflectUtil.getFieldValue(model, fieldName);

                if (fieldJsonData == null && !cacheHelper.existSessionUploadFile(filename)) {
                    ResponseResult.output(HttpServletResponse.SC_BAD_REQUEST);
                    return;
                }
                if (!BaseUpDownloader.containFile(fieldJsonData, filename)
                        && !cacheHelper.existSessionUploadFile(filename)) {
                    ResponseResult.output(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            }

            UploadStoreInfo storeInfo = MyModelUtil.getUploadStoreInfo(clazz, fieldName);
            if (!storeInfo.isSupportUpload()) {
                ResponseResult.output(HttpServletResponse.SC_NOT_IMPLEMENTED,
                        ResponseResult.error(ErrorCodeEnum.INVALID_UPLOAD_FIELD));
                return;
            }
            BaseUpDownloader upDownloader = upDownloaderFactory.get(storeInfo.getStoreType());
            upDownloader.doDownload(appConfig.getUploadFileBaseDir(),
                    clazz.getSimpleName(), fieldName, filename, asImage, response);
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            log.error(e.getMessage(), e);
        }
    }


    /**
     * 这个可以给设备获取图片用，，，
     *
     * @param url
     * @param response
     * @throws IOException
     */
    @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false)
    @GetMapping("/downloadByUrl")
    @SaIgnore
    public void downloadByUrl(
            @RequestParam String url,
            HttpServletResponse response) throws IOException {
        // 解码 URL
        url = URLDecoder.decode(url, String.valueOf(StandardCharsets.UTF_8));

        // 将反斜杠替换为正斜杠
        url = url.replace("\\", "/");

        // 转化为绝对路径
        String s = System.getProperty("user.dir") + url.substring(1);
        File file = new File(s);

        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        response.setContentType("application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));

        try (FileInputStream fileInputStream = new FileInputStream(file);
             ServletOutputStream out = response.getOutputStream()) {
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) != -1) {
                out.write(bytes, 0, len);
            }
            out.flush();
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            e.printStackTrace();
        }
    }


}
